/* * RISC-V Emulation Helpers for QEMU. * * Copyright (c) 2016-2017 Sagar Karandikar, sagark@eecs.berkeley.edu * Copyright (c) 2017-2018 SiFive, Inc. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2 or later, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ #include "qemu/osdep.h" #include "qemu/log.h" #include "cpu.h" #include "qemu/main-loop.h" #include "exec/exec-all.h" #include "exec/helper-proto.h" #ifndef CONFIG_USER_ONLY #if defined(TARGET_RISCV32) static const char valid_vm_1_09[16] = { [VM_1_09_MBARE] = 1, [VM_1_09_SV32] = 1, }; static const char valid_vm_1_10[16] = { [VM_1_10_MBARE] = 1, [VM_1_10_SV32] = 1 }; #elif defined(TARGET_RISCV64) static const char valid_vm_1_09[16] = { [VM_1_09_MBARE] = 1, [VM_1_09_SV39] = 1, [VM_1_09_SV48] = 1, }; static const char valid_vm_1_10[16] = { [VM_1_10_MBARE] = 1, [VM_1_10_SV39] = 1, [VM_1_10_SV48] = 1, [VM_1_10_SV57] = 1 }; #endif static int validate_vm(CPURISCVState *env, target_ulong vm) { return (env->priv_ver >= PRIV_VERSION_1_10_0) ? valid_vm_1_10[vm & 0xf] : valid_vm_1_09[vm & 0xf]; } #endif /* Exceptions processing helpers */ void QEMU_NORETURN do_raise_exception_err(CPURISCVState *env, uint32_t exception, uintptr_t pc) { CPUState *cs = CPU(riscv_env_get_cpu(env)); qemu_log_mask(CPU_LOG_INT, "%s: %d\n", __func__, exception); cs->exception_index = exception; cpu_loop_exit_restore(cs, pc); } void helper_raise_exception(CPURISCVState *env, uint32_t exception) { do_raise_exception_err(env, exception, 0); } static void validate_mstatus_fs(CPURISCVState *env, uintptr_t ra) { #ifndef CONFIG_USER_ONLY if (!(env->mstatus & MSTATUS_FS)) { do_raise_exception_err(env, RISCV_EXCP_ILLEGAL_INST, ra); } #endif } /* * Handle writes to CSRs and any resulting special behavior * * Adapted from Spike's processor_t::set_csr */ void csr_write_helper(CPURISCVState *env, target_ulong val_to_write, target_ulong csrno) { #ifndef CONFIG_USER_ONLY uint64_t delegable_ints = MIP_SSIP | MIP_STIP | MIP_SEIP | (1 << IRQ_X_COP); uint64_t all_ints = delegable_ints | MIP_MSIP | MIP_MTIP; #endif switch (csrno) { case CSR_FFLAGS: validate_mstatus_fs(env, GETPC()); cpu_riscv_set_fflags(env, val_to_write & (FSR_AEXC >> FSR_AEXC_SHIFT)); break; case CSR_FRM: validate_mstatus_fs(env, GETPC()); env->frm = val_to_write & (FSR_RD >> FSR_RD_SHIFT); break; case CSR_FCSR: validate_mstatus_fs(env, GETPC()); env->frm = (val_to_write & FSR_RD) >> FSR_RD_SHIFT; cpu_riscv_set_fflags(env, (val_to_write & FSR_AEXC) >> FSR_AEXC_SHIFT); break; #ifndef CONFIG_USER_ONLY case CSR_MSTATUS: { target_ulong mstatus = env->mstatus; target_ulong mask = 0; target_ulong mpp = get_field(val_to_write, MSTATUS_MPP); /* flush tlb on mstatus fields that affect VM */ if (env->priv_ver <= PRIV_VERSION_1_09_1) { if ((val_to_write ^ mstatus) & (MSTATUS_MXR | MSTATUS_MPP | MSTATUS_MPRV | MSTATUS_SUM | MSTATUS_VM)) { helper_tlb_flush(env); } mask = MSTATUS_SIE | MSTATUS_SPIE | MSTATUS_MIE | MSTATUS_MPIE | MSTATUS_SPP | MSTATUS_FS | MSTATUS_MPRV | MSTATUS_SUM | MSTATUS_MPP | MSTATUS_MXR | (validate_vm(env, get_field(val_to_write, MSTATUS_VM)) ? MSTATUS_VM : 0); } if (env->priv_ver >= PRIV_VERSION_1_10_0) { if ((val_to_write ^ mstatus) & (MSTATUS_MXR | MSTATUS_MPP | MSTATUS_MPRV | MSTATUS_SUM)) { helper_tlb_flush(env); } mask = MSTATUS_SIE | MSTATUS_SPIE | MSTATUS_MIE | MSTATUS_MPIE | MSTATUS_SPP | MSTATUS_FS | MSTATUS_MPRV | MSTATUS_SUM | MSTATUS_MPP | MSTATUS_MXR; } /* silenty discard mstatus.mpp writes for unsupported modes */ if (mpp == PRV_H || (!riscv_has_ext(env, RVS) && mpp == PRV_S) || (!riscv_has_ext(env, RVU) && mpp == PRV_U)) { mask &= ~MSTATUS_MPP; } mstatus = (mstatus & ~mask) | (val_to_write & mask); /* Note: this is a workaround for an issue where mstatus.FS does not report dirty after floating point operations that modify floating point state. This workaround is technically compliant with the RISC-V Privileged specification as it is legal to return only off, or dirty. at the expense of extra floating point save/restore. */ /* FP is always dirty or off */ if (mstatus & MSTATUS_FS) { mstatus |= MSTATUS_FS; } int dirty = ((mstatus & MSTATUS_FS) == MSTATUS_FS) | ((mstatus & MSTATUS_XS) == MSTATUS_XS); mstatus = set_field(mstatus, MSTATUS_SD, dirty); env->mstatus = mstatus; break; } case CSR_MIP: { /* * Since the writeable bits in MIP are not set asynchrously by the * CLINT, no additional locking is needed for read-modifiy-write * CSR operations */ qemu_mutex_lock_iothread(); RISCVCPU *cpu = riscv_env_get_cpu(env); riscv_set_local_interrupt(cpu, MIP_SSIP, (val_to_write & MIP_SSIP) != 0); riscv_set_local_interrupt(cpu, MIP_STIP, (val_to_write & MIP_STIP) != 0); /* * csrs, csrc on mip.SEIP is not decomposable into separate read and * write steps, so a different implementation is needed */ qemu_mutex_unlock_iothread(); break; } case CSR_MIE: { env->mie = (env->mie & ~all_ints) | (val_to_write & all_ints); break; } case CSR_MIDELEG: env->mideleg = (env->mideleg & ~delegable_ints) | (val_to_write & delegable_ints); break; case CSR_MEDELEG: { target_ulong mask = 0; mask |= 1ULL << (RISCV_EXCP_INST_ADDR_MIS); mask |= 1ULL << (RISCV_EXCP_INST_ACCESS_FAULT); mask |= 1ULL << (RISCV_EXCP_ILLEGAL_INST); mask |= 1ULL << (RISCV_EXCP_BREAKPOINT); mask |= 1ULL << (RISCV_EXCP_LOAD_ADDR_MIS); mask |= 1ULL << (RISCV_EXCP_LOAD_ACCESS_FAULT); mask |= 1ULL << (RISCV_EXCP_STORE_AMO_ADDR_MIS); mask |= 1ULL << (RISCV_EXCP_STORE_AMO_ACCESS_FAULT); mask |= 1ULL << (RISCV_EXCP_U_ECALL); mask |= 1ULL << (RISCV_EXCP_S_ECALL); mask |= 1ULL << (RISCV_EXCP_H_ECALL); mask |= 1ULL << (RISCV_EXCP_M_ECALL); mask |= 1ULL << (RISCV_EXCP_INST_PAGE_FAULT); mask |= 1ULL << (RISCV_EXCP_LOAD_PAGE_FAULT); mask |= 1ULL << (RISCV_EXCP_STORE_PAGE_FAULT); env->medeleg = (env->medeleg & ~mask) | (val_to_write & mask); break; } case CSR_MINSTRET: /* minstret is WARL so unsupported writes are ignored */ break; case CSR_MCYCLE: /* mcycle is WARL so unsupported writes are ignored */ break; #if defined(TARGET_RISCV32) case CSR_MINSTRETH: /* minstreth is WARL so unsupported writes are ignored */ break; case CSR_MCYCLEH: /* mcycleh is WARL so unsupported writes are ignored */ break; #endif case CSR_MUCOUNTEREN: if (env->priv_ver <= PRIV_VERSION_1_09_1) { env->scounteren = val_to_write; break; } else { goto do_illegal; } case CSR_MSCOUNTEREN: if (env->priv_ver <= PRIV_VERSION_1_09_1) { env->mcounteren = val_to_write; break; } else { goto do_illegal; } case CSR_SSTATUS: { target_ulong ms = env->mstatus; target_ulong mask = SSTATUS_SIE | SSTATUS_SPIE | SSTATUS_UIE | SSTATUS_UPIE | SSTATUS_SPP | SSTATUS_FS | SSTATUS_XS | SSTATUS_SUM | SSTATUS_SD; if (env->priv_ver >= PRIV_VERSION_1_10_0) { mask |= SSTATUS_MXR; } ms = (ms & ~mask) | (val_to_write & mask); csr_write_helper(env, ms, CSR_MSTATUS); break; } case CSR_SIP: { qemu_mutex_lock_iothread(); target_ulong next_mip = (env->mip & ~env->mideleg) | (val_to_write & env->mideleg); qemu_mutex_unlock_iothread(); csr_write_helper(env, next_mip, CSR_MIP); break; } case CSR_SIE: { target_ulong next_mie = (env->mie & ~env->mideleg) | (val_to_write & env->mideleg); csr_write_helper(env, next_mie, CSR_MIE); break; } case CSR_SATP: /* CSR_SPTBR */ { if (!riscv_feature(env, RISCV_FEATURE_MMU)) { break; } if (env->priv_ver <= PRIV_VERSION_1_09_1 && (val_to_write ^ env->sptbr)) { helper_tlb_flush(env); env->sptbr = val_to_write & (((target_ulong) 1 << (TARGET_PHYS_ADDR_SPACE_BITS - PGSHIFT)) - 1); } if (env->priv_ver >= PRIV_VERSION_1_10_0 && validate_vm(env, get_field(val_to_write, SATP_MODE)) && ((val_to_write ^ env->satp) & (SATP_MODE | SATP_ASID | SATP_PPN))) { helper_tlb_flush(env); env->satp = val_to_write; } break; } case CSR_SEPC: env->sepc = val_to_write; break; case CSR_STVEC: /* bits [1:0] encode mode; 0 = direct, 1 = vectored, 2 >= reserved */ if ((val_to_write & 3) == 0) { env->stvec = val_to_write >> 2 << 2; } else { qemu_log_mask(LOG_UNIMP, "CSR_STVEC: vectored traps not supported"); } break; case CSR_SCOUNTEREN: if (env->priv_ver >= PRIV_VERSION_1_10_0) { env->scounteren = val_to_write; break; } else { goto do_illegal; } case CSR_SSCRATCH: env->sscratch = val_to_write; break; case CSR_SCAUSE: env->scause = val_to_write; break; case CSR_SBADADDR: env->sbadaddr = val_to_write; break; case CSR_MEPC: env->mepc = val_to_write; break; case CSR_MTVEC: /* bits [1:0] indicate mode; 0 = direct, 1 = vectored, 2 >= reserved */ if ((val_to_write & 3) == 0) { env->mtvec = val_to_write >> 2 << 2; } else { qemu_log_mask(LOG_UNIMP, "CSR_MTVEC: vectored traps not supported"); } break; case CSR_MCOUNTEREN: if (env->priv_ver >= PRIV_VERSION_1_10_0) { env->mcounteren = val_to_write; break; } else { goto do_illegal; } case CSR_MSCRATCH: env->mscratch = val_to_write; break; case CSR_MCAUSE: env->mcause = val_to_write; break; case CSR_MBADADDR: env->mbadaddr = val_to_write; break; case CSR_MISA: /* misa is WARL so unsupported writes are ignored */ break; case CSR_PMPCFG0: case CSR_PMPCFG1: case CSR_PMPCFG2: case CSR_PMPCFG3: pmpcfg_csr_write(env, csrno - CSR_PMPCFG0, val_to_write); break; case CSR_PMPADDR0: case CSR_PMPADDR1: case CSR_PMPADDR2: case CSR_PMPADDR3: case CSR_PMPADDR4: case CSR_PMPADDR5: case CSR_PMPADDR6: case CSR_PMPADDR7: case CSR_PMPADDR8: case CSR_PMPADDR9: case CSR_PMPADDR10: case CSR_PMPADDR11: case CSR_PMPADDR12: case CSR_PMPADDR13: case CSR_PMPADDR14: case CSR_PMPADDR15: pmpaddr_csr_write(env, csrno - CSR_PMPADDR0, val_to_write); break; #endif #if !defined(CONFIG_USER_ONLY) do_illegal: #endif default: do_raise_exception_err(env, RISCV_EXCP_ILLEGAL_INST, GETPC()); } } /* * Handle reads to CSRs and any resulting special behavior * * Adapted from Spike's processor_t::get_csr */ target_ulong csr_read_helper(CPURISCVState *env, target_ulong csrno) { #ifndef CONFIG_USER_ONLY target_ulong ctr_en = env->priv == PRV_U ? env->scounteren : env->priv == PRV_S ? env->mcounteren : -1U; #else target_ulong ctr_en = -1; #endif target_ulong ctr_ok = (ctr_en >> (csrno & 31)) & 1; if (csrno >= CSR_HPMCOUNTER3 && csrno <= CSR_HPMCOUNTER31) { if (ctr_ok) { return 0; } } #if defined(TARGET_RISCV32) if (csrno >= CSR_HPMCOUNTER3H && csrno <= CSR_HPMCOUNTER31H) { if (ctr_ok) { return 0; } } #endif if (csrno >= CSR_MHPMCOUNTER3 && csrno <= CSR_MHPMCOUNTER31) { return 0; } #if defined(TARGET_RISCV32) if (csrno >= CSR_MHPMCOUNTER3 && csrno <= CSR_MHPMCOUNTER31) { return 0; } #endif if (csrno >= CSR_MHPMEVENT3 && csrno <= CSR_MHPMEVENT31) { return 0; } switch (csrno) { case CSR_FFLAGS: validate_mstatus_fs(env, GETPC()); return cpu_riscv_get_fflags(env); case CSR_FRM: validate_mstatus_fs(env, GETPC()); return env->frm; case CSR_FCSR: validate_mstatus_fs(env, GETPC()); return (cpu_riscv_get_fflags(env) << FSR_AEXC_SHIFT) | (env->frm << FSR_RD_SHIFT); /* rdtime/rdtimeh is trapped and emulated by bbl in system mode */ #ifdef CONFIG_USER_ONLY case CSR_TIME: return cpu_get_host_ticks(); #if defined(TARGET_RISCV32) case CSR_TIMEH: return cpu_get_host_ticks() >> 32; #endif #endif case CSR_INSTRET: case CSR_CYCLE: if (ctr_ok) { #if !defined(CONFIG_USER_ONLY) if (use_icount) { return cpu_get_icount(); } else { return cpu_get_host_ticks(); } #else return cpu_get_host_ticks(); #endif } break; #if defined(TARGET_RISCV32) case CSR_INSTRETH: case CSR_CYCLEH: if (ctr_ok) { #if !defined(CONFIG_USER_ONLY) if (use_icount) { return cpu_get_icount() >> 32; } else { return cpu_get_host_ticks() >> 32; } #else return cpu_get_host_ticks() >> 32; #endif } break; #endif #ifndef CONFIG_USER_ONLY case CSR_MINSTRET: case CSR_MCYCLE: if (use_icount) { return cpu_get_icount(); } else { return cpu_get_host_ticks(); } case CSR_MINSTRETH: case CSR_MCYCLEH: #if defined(TARGET_RISCV32) if (use_icount) { return cpu_get_icount() >> 32; } else { return cpu_get_host_ticks() >> 32; } #endif break; case CSR_MUCOUNTEREN: if (env->priv_ver <= PRIV_VERSION_1_09_1) { return env->scounteren; } else { break; /* illegal instruction */ } case CSR_MSCOUNTEREN: if (env->priv_ver <= PRIV_VERSION_1_09_1) { return env->mcounteren; } else { break; /* illegal instruction */ } case CSR_SSTATUS: { target_ulong mask = SSTATUS_SIE | SSTATUS_SPIE | SSTATUS_UIE | SSTATUS_UPIE | SSTATUS_SPP | SSTATUS_FS | SSTATUS_XS | SSTATUS_SUM | SSTATUS_SD; if (env->priv_ver >= PRIV_VERSION_1_10_0) { mask |= SSTATUS_MXR; } return env->mstatus & mask; } case CSR_SIP: { qemu_mutex_lock_iothread(); target_ulong tmp = env->mip & env->mideleg; qemu_mutex_unlock_iothread(); return tmp; } case CSR_SIE: return env->mie & env->mideleg; case CSR_SEPC: return env->sepc; case CSR_SBADADDR: return env->sbadaddr; case CSR_STVEC: return env->stvec; case CSR_SCOUNTEREN: if (env->priv_ver >= PRIV_VERSION_1_10_0) { return env->scounteren; } else { break; /* illegal instruction */ } case CSR_SCAUSE: return env->scause; case CSR_SATP: /* CSR_SPTBR */ if (!riscv_feature(env, RISCV_FEATURE_MMU)) { return 0; } if (env->priv_ver >= PRIV_VERSION_1_10_0) { return env->satp; } else { return env->sptbr; } case CSR_SSCRATCH: return env->sscratch; case CSR_MSTATUS: return env->mstatus; case CSR_MIP: { qemu_mutex_lock_iothread(); target_ulong tmp = env->mip; qemu_mutex_unlock_iothread(); return tmp; } case CSR_MIE: return env->mie; case CSR_MEPC: return env->mepc; case CSR_MSCRATCH: return env->mscratch; case CSR_MCAUSE: return env->mcause; case CSR_MBADADDR: return env->mbadaddr; case CSR_MISA: return env->misa; case CSR_MARCHID: return 0; /* as spike does */ case CSR_MIMPID: return 0; /* as spike does */ case CSR_MVENDORID: return 0; /* as spike does */ case CSR_MHARTID: return env->mhartid; case CSR_MTVEC: return env->mtvec; case CSR_MCOUNTEREN: if (env->priv_ver >= PRIV_VERSION_1_10_0) { return env->mcounteren; } else { break; /* illegal instruction */ } case CSR_MEDELEG: return env->medeleg; case CSR_MIDELEG: return env->mideleg; case CSR_PMPCFG0: case CSR_PMPCFG1: case CSR_PMPCFG2: case CSR_PMPCFG3: return pmpcfg_csr_read(env, csrno - CSR_PMPCFG0); case CSR_PMPADDR0: case CSR_PMPADDR1: case CSR_PMPADDR2: case CSR_PMPADDR3: case CSR_PMPADDR4: case CSR_PMPADDR5: case CSR_PMPADDR6: case CSR_PMPADDR7: case CSR_PMPADDR8: case CSR_PMPADDR9: case CSR_PMPADDR10: case CSR_PMPADDR11: case CSR_PMPADDR12: case CSR_PMPADDR13: case CSR_PMPADDR14: case CSR_PMPADDR15: return pmpaddr_csr_read(env, csrno - CSR_PMPADDR0); #endif } /* used by e.g. MTIME read */ do_raise_exception_err(env, RISCV_EXCP_ILLEGAL_INST, GETPC()); } /* * Check that CSR access is allowed. * * Adapted from Spike's decode.h:validate_csr */ static void validate_csr(CPURISCVState *env, uint64_t which, uint64_t write, uintptr_t ra) { #ifndef CONFIG_USER_ONLY unsigned csr_priv = get_field((which), 0x300); unsigned csr_read_only = get_field((which), 0xC00) == 3; if (((write) && csr_read_only) || (env->priv < csr_priv)) { do_raise_exception_err(env, RISCV_EXCP_ILLEGAL_INST, ra); } #endif } target_ulong helper_csrrw(CPURISCVState *env, target_ulong src, target_ulong csr) { validate_csr(env, csr, 1, GETPC()); uint64_t csr_backup = csr_read_helper(env, csr); csr_write_helper(env, src, csr); return csr_backup; } target_ulong helper_csrrs(CPURISCVState *env, target_ulong src, target_ulong csr, target_ulong rs1_pass) { validate_csr(env, csr, rs1_pass != 0, GETPC()); uint64_t csr_backup = csr_read_helper(env, csr); if (rs1_pass != 0) { csr_write_helper(env, src | csr_backup, csr); } return csr_backup; } target_ulong helper_csrrc(CPURISCVState *env, target_ulong src, target_ulong csr, target_ulong rs1_pass) { validate_csr(env, csr, rs1_pass != 0, GETPC()); uint64_t csr_backup = csr_read_helper(env, csr); if (rs1_pass != 0) { csr_write_helper(env, (~src) & csr_backup, csr); } return csr_backup; } #ifndef CONFIG_USER_ONLY /* iothread_mutex must be held */ void riscv_set_local_interrupt(RISCVCPU *cpu, target_ulong mask, int value) { target_ulong old_mip = cpu->env.mip; cpu->env.mip = (old_mip & ~mask) | (value ? mask : 0); if (cpu->env.mip && !old_mip) { cpu_interrupt(CPU(cpu), CPU_INTERRUPT_HARD); } else if (!cpu->env.mip && old_mip) { cpu_reset_interrupt(CPU(cpu), CPU_INTERRUPT_HARD); } } void riscv_set_mode(CPURISCVState *env, target_ulong newpriv) { if (newpriv > PRV_M) { g_assert_not_reached(); } if (newpriv == PRV_H) { newpriv = PRV_U; } /* tlb_flush is unnecessary as mode is contained in mmu_idx */ env->priv = newpriv; } target_ulong helper_sret(CPURISCVState *env, target_ulong cpu_pc_deb) { if (!(env->priv >= PRV_S)) { do_raise_exception_err(env, RISCV_EXCP_ILLEGAL_INST, GETPC()); } target_ulong retpc = env->sepc; if (!riscv_has_ext(env, RVC) && (retpc & 0x3)) { do_raise_exception_err(env, RISCV_EXCP_INST_ADDR_MIS, GETPC()); } target_ulong mstatus = env->mstatus; target_ulong prev_priv = get_field(mstatus, MSTATUS_SPP); mstatus = set_field(mstatus, env->priv_ver >= PRIV_VERSION_1_10_0 ? MSTATUS_SIE : MSTATUS_UIE << prev_priv, get_field(mstatus, MSTATUS_SPIE)); mstatus = set_field(mstatus, MSTATUS_SPIE, 0); mstatus = set_field(mstatus, MSTATUS_SPP, PRV_U); riscv_set_mode(env, prev_priv); csr_write_helper(env, mstatus, CSR_MSTATUS); return retpc; } target_ulong helper_mret(CPURISCVState *env, target_ulong cpu_pc_deb) { if (!(env->priv >= PRV_M)) { do_raise_exception_err(env, RISCV_EXCP_ILLEGAL_INST, GETPC()); } target_ulong retpc = env->mepc; if (!riscv_has_ext(env, RVC) && (retpc & 0x3)) { do_raise_exception_err(env, RISCV_EXCP_INST_ADDR_MIS, GETPC()); } target_ulong mstatus = env->mstatus; target_ulong prev_priv = get_field(mstatus, MSTATUS_MPP); mstatus = set_field(mstatus, env->priv_ver >= PRIV_VERSION_1_10_0 ? MSTATUS_MIE : MSTATUS_UIE << prev_priv, get_field(mstatus, MSTATUS_MPIE)); mstatus = set_field(mstatus, MSTATUS_MPIE, 0); mstatus = set_field(mstatus, MSTATUS_MPP, PRV_U); riscv_set_mode(env, prev_priv); csr_write_helper(env, mstatus, CSR_MSTATUS); return retpc; } void helper_wfi(CPURISCVState *env) { CPUState *cs = CPU(riscv_env_get_cpu(env)); cs->halted = 1; cs->exception_index = EXCP_HLT; cpu_loop_exit(cs); } void helper_tlb_flush(CPURISCVState *env) { RISCVCPU *cpu = riscv_env_get_cpu(env); CPUState *cs = CPU(cpu); tlb_flush(cs); } #endif /* !CONFIG_USER_ONLY */