b02403363f
This change is a workaround for a bug where mstatus.FS is not correctly reporting dirty after operations that modify floating point registers. This a critical bug or RISC-V in QEMU as it results in floating point register file corruption when running SMP Linux due to task migration and possibly uniprocessor Linux if more than one process is using the FPU. This workaround will return dirty if mstatus.FS is switched from off to initial or clean. According to the specification it is legal for an implementation to return only off, or dirty. Cc: Palmer Dabbelt <palmer@sifive.com> Cc: Sagar Karandikar <sagark@eecs.berkeley.edu> Cc: Bastian Koppelmann <kbastian@mail.uni-paderborn.de> Cc: Peter Maydell <peter.maydell@linaro.org> Cc: Alex Bennée <alex.bennee@linaro.org> Cc: Richard Henderson <richard.henderson@linaro.org> Cc: Philippe Mathieu-Daudé <f4bug@amsat.org> Tested-by: Richard W.M. Jones <rjones@redhat.com> Signed-off-by: Michael Clark <mjc@sifive.com> Reviewed-by: Richard Henderson <richard.henderson@linaro.org>
683 lines
20 KiB
C
683 lines
20 KiB
C
/*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#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:
|
|
qemu_log_mask(LOG_UNIMP, "CSR_MINSTRET: write not implemented");
|
|
goto do_illegal;
|
|
case CSR_MCYCLE:
|
|
qemu_log_mask(LOG_UNIMP, "CSR_MCYCLE: write not implemented");
|
|
goto do_illegal;
|
|
case CSR_MINSTRETH:
|
|
qemu_log_mask(LOG_UNIMP, "CSR_MINSTRETH: write not implemented");
|
|
goto do_illegal;
|
|
case CSR_MCYCLEH:
|
|
qemu_log_mask(LOG_UNIMP, "CSR_MCYCLEH: write not implemented");
|
|
goto do_illegal;
|
|
case CSR_MUCOUNTEREN:
|
|
env->mucounteren = val_to_write;
|
|
break;
|
|
case CSR_MSCOUNTEREN:
|
|
env->mscounteren = val_to_write;
|
|
break;
|
|
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_MXR | SSTATUS_SD;
|
|
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)) {
|
|
goto do_illegal;
|
|
}
|
|
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:
|
|
if (val_to_write & 1) {
|
|
qemu_log_mask(LOG_UNIMP, "CSR_STVEC: vectored traps not supported");
|
|
goto do_illegal;
|
|
}
|
|
env->stvec = val_to_write >> 2 << 2;
|
|
break;
|
|
case CSR_SCOUNTEREN:
|
|
env->scounteren = val_to_write;
|
|
break;
|
|
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:
|
|
if (val_to_write & 1) {
|
|
qemu_log_mask(LOG_UNIMP, "CSR_MTVEC: vectored traps not supported");
|
|
goto do_illegal;
|
|
}
|
|
env->mtvec = val_to_write >> 2 << 2;
|
|
break;
|
|
case CSR_MCOUNTEREN:
|
|
env->mcounteren = val_to_write;
|
|
break;
|
|
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: {
|
|
qemu_log_mask(LOG_UNIMP, "CSR_MISA: misa writes not supported");
|
|
goto do_illegal;
|
|
}
|
|
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;
|
|
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->mucounteren :
|
|
env->priv == PRV_S ? env->mscounteren : -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) {
|
|
return cpu_get_host_ticks();
|
|
}
|
|
break;
|
|
#if defined(TARGET_RISCV32)
|
|
case CSR_INSTRETH:
|
|
case CSR_CYCLEH:
|
|
if (ctr_ok) {
|
|
return cpu_get_host_ticks() >> 32;
|
|
}
|
|
break;
|
|
#endif
|
|
#ifndef CONFIG_USER_ONLY
|
|
case CSR_MINSTRET:
|
|
case CSR_MCYCLE:
|
|
return cpu_get_host_ticks();
|
|
case CSR_MINSTRETH:
|
|
case CSR_MCYCLEH:
|
|
#if defined(TARGET_RISCV32)
|
|
return cpu_get_host_ticks() >> 32;
|
|
#endif
|
|
break;
|
|
case CSR_MUCOUNTEREN:
|
|
return env->mucounteren;
|
|
case CSR_MSCOUNTEREN:
|
|
return env->mscounteren;
|
|
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:
|
|
return env->scounteren;
|
|
case CSR_SCAUSE:
|
|
return env->scause;
|
|
case CSR_SPTBR:
|
|
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:
|
|
return env->mcounteren;
|
|
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 */
|